#include "bsp/bsp.h"
#include "board.h"
#include "os/types.h"
#include "utils/utils.h"
#include <string.h>

#define one_page_size CONFIG_FLASH_PAGE_SIZE


#if defined (GD32F30X_HD) || defined (GD32F30X_XD) || defined (GD32F30X_CL)
#define FMC_FLAG_PGERR  FMC_FLAG_BANK0_PGERR
#define FMC_FLAG_PGAERR  FMC_FLAG_BANK0_PGERR
#define FMC_FLAG_WPERR  FMC_FLAG_BANK0_WPERR
#define FMC_FLAG_END  FMC_FLAG_BANK0_END
#endif

static void flash_clear_error() {
    fmc_flag_clear(FMC_FLAG_PGERR | FMC_FLAG_WPERR | FMC_FLAG_END);
}

static void flash_unlock(void)
{
  	fmc_unlock();
}

/**
  * @brief  Locks the FLASH control register access
  * @retval HAL Status
  */
static void flash_lock(void)
{
  	/* Set the LOCK Bit to lock the FLASH Registers access */
  	fmc_lock();
}

// @brief Erases a flash sector. This sets all bits in the sector to 1.
// The sector's current index is reset to the minimum value (n_reserved).
// @returns 0 on success or a non-zero error code otherwise
int flash_erase_page(u32 address) {
	wdog_reload();
    flash_clear_error();
	flash_unlock();
	fmc_state_enum result = fmc_page_erase(address);
	flash_lock();
    return (FMC_READY == result)?0:-1;
}

int flash_erase_pages(u32 address, s32 len) {
  	uint32_t pages = (len + one_page_size - 1)/one_page_size;
	for (int page = 0; page < pages; page++) {
		flash_erase_page(address + page * one_page_size);
	}
	return 0;
}


int flash_write_page(u32 addr, uint8_t *data, u32 len, bool erase) {
    if (erase && flash_erase_page(addr) != 0) {
        return -1;
    }
	wdog_reload();
	flash_unlock();
	int total_words = len / 4;
	uint32_t *p_u32_data = (uint32_t *)data;
	int i;
	for (i = 0; i < total_words; i++){
		flash_clear_error();
		fmc_word_program(addr, p_u32_data[i]);
		data += 4;
		addr += 4;
	}

	int remain_len = len - total_words * 4;
	if (remain_len > 0){
		uint32_t words = 0;
		for (int i = 0; i < remain_len; i++){
			words |= data[i] << (8*i);
		}
		flash_clear_error();
		fmc_word_program(addr, words);
	}
	flash_lock();
    return 0;
}

#if 0
void flash_write_magic(u32 magic) {
	u32 buffer[16];
	if (magic == REG32(CONFIG_IAP_INFO_ADDR + 8)) {
		return;
	}
	memcpy(buffer, (void *)CONFIG_IAP_INFO_ADDR, sizeof(buffer));

	buffer[2] = magic;

	flash_write_page(CONFIG_IAP_INFO_ADDR, (u8 *)buffer, sizeof(buffer), true);
}

u32 flash_read_magic(void) {
	return REG32(CONFIG_IAP_INFO_ADDR + 8);
}
#endif

